package com.zmp.kotlinnote.view;

import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by zmp on 2018/12/11.
 */
public class SvgPathHelper {
    private int svgPathLength = 0;
    private String svgPath = null;
    private int mIndex;
    private static final String TAG = "SvgPathHelper";
    private List<Integer> cmdPositions = new ArrayList<>();

    private void findCommand() {
        cmdPositions.clear();
        while (mIndex < svgPathLength) {
            char c = svgPath.charAt(mIndex);
            if ('A' <= c && c <= 'Z') {
                cmdPositions.add(mIndex);
            } else if ('a' <= c && c <= 'z') {
                cmdPositions.add(mIndex);
            }
            ++mIndex;
        }
    }

    /**
     * M x,y
     * L x,y
     * H x
     * V y
     * C x1,y1,x2,y2,x,y
     * Q x1,y1,x,y
     * S x2,y2,x,y
     * T x,y
     */
    public Path parserSvgPath(String svgPath) {
        this.svgPath = svgPath;
        svgPathLength = svgPath.length();
        mIndex = 0;
        Path lPath = new Path();
        Path path = new Path();
        path.lineTo(0F, 0F);
        lPath.setFillType(Path.FillType.WINDING);
        //记录最后一个操作点
        PointF lastPoint = new PointF();
        PointF startPoint = new PointF();
        PointF cubPoint = new PointF();
        findCommand();

        for (int i = 0; i < cmdPositions.size(); i++) {
            Integer index = cmdPositions.get(i);
            switch (svgPath.charAt(index)) {
                case 'm': {
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size(); i1++) {
                        if (i1 % 2 == 0) {
                            lastPoint.offset(ps.get(i1), 0F);
                        } else {
                            lastPoint.offset(0F, ps.get(i1));
                            lPath.moveTo(lastPoint.x, lastPoint.y);
                            startPoint.set(lastPoint.x, lastPoint.y);
                            Log.e(TAG, "parser: lastPoint:m" + lastPoint);
                        }
                    }
                }
                break;
                case 'M': {
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size(); i1++) {
                        if (i1 % 2 == 0) {
                            lastPoint.x = ps.get(i1);
                        } else {
                            lastPoint.y = ps.get(i1);
                            lPath.moveTo(lastPoint.x, lastPoint.y);
                            startPoint.set(lastPoint.x, lastPoint.y);
                            Log.e(TAG, "parser: lastPoint:M" + lastPoint);
                        }
                    }
                }
                break;
                case 'l': {
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size(); i1++) {
                        if (i1 % 2 == 0) {
                            lastPoint.offset(ps.get(i1), 0F);
                        } else {
                            lastPoint.offset(0F, ps.get(i1));
                            lPath.lineTo(lastPoint.x, lastPoint.y);
                            Log.e(TAG, "parser: lastPoint l-----------:" + lastPoint + ",i---:" + i);
                        }
                    }
                    break;
                }
                case 'L': {
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size(); i1++) {
                        if (i1 % 2 == 0) {
                            lastPoint.x = ps.get(i1);
                        } else {
                            lastPoint.y = ps.get(i1);
                            lPath.lineTo(lastPoint.x, lastPoint.y);
                            Log.e(TAG, "parser: lastPoint L-----------:" + lastPoint + ",i---:" + i);
                        }
                    }
                    break;
                }
                case 'h': {//基于上个坐标在水平方向上划线，因此y轴不变
                    ArrayList<Float> ps = findNum(i);
                    lastPoint.offset(ps.get(0), 0);
                    lPath.lineTo(lastPoint.x, lastPoint.y);
                    Log.e(TAG, "parser: lastPoint h:" + lastPoint);
                    break;
                }

                case 'H': {//基于上个坐标在水平方向上划线，因此y轴不变
                    ArrayList<Float> ps = findNum(i);
                    lastPoint.set(ps.get(0), lastPoint.y);
                    lPath.lineTo(lastPoint.x, lastPoint.y);
                    Log.e(TAG, "parser: lastPoint H:" + lastPoint);
                    break;
                }
                case 'v': {
                    ArrayList<Float> ps = findNum(i);
                    lastPoint.offset(0, ps.get(0));
                    lPath.lineTo(lastPoint.x, lastPoint.y);
                    Log.e(TAG, "parser: lastPoint V:" + lastPoint);
                    break;
                }
                case 'V': {//基于上个坐标在水平方向上划线，因此x轴不变
                    ArrayList<Float> ps = findNum(i);
                    lastPoint.set(lastPoint.x, ps.get(0));
                    lPath.lineTo(lastPoint.x, lastPoint.y);
                    Log.e(TAG, "parser: lastPoint V:" + lastPoint);
                    break;
                }
                case 'c': {
                    final ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size() / 6; i1++) {
                        float p0 = ps.get(i1 * 6);
                        float p1 = ps.get(i1 * 6 + 1);
                        float p2 = ps.get(i1 * 6 + 2);
                        float p3 = ps.get(i1 * 6 + 3);
                        float p4 = ps.get(i1 * 6 + 4);
                        float p5 = ps.get(i1 * 6 + 5);
                        lPath.cubicTo(lastPoint.x + p0, lastPoint.y + p1, lastPoint.x + p2, lastPoint.y + p3, lastPoint.x + p4, lastPoint.y + p5);
                        cubPoint.set(lastPoint.x + p2, lastPoint.y + p3);
                        lastPoint.offset(p4, p5);
                        Log.e(TAG, "parser: lastPoint c:" + lastPoint);
                    }
                    break;
                }
                case 'C': {//3次贝塞尔曲线
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size() / 6; i1++) {
                        lPath.cubicTo(ps.get(i1 * 6), ps.get(i1 * 6 + 1), ps.get(i1 * 6 + 2), ps.get(i1 * 6 + 3), ps.get(i1 * 6 + 4), ps.get(i1 * 6 + 5));
                        lastPoint.set(ps.get(i1 * 6 + 4), ps.get(i1 * 6 + 5));
                        Log.e(TAG, "parser: lastPoint C:" + lastPoint);
                        cubPoint.set(ps.get(i1 * 6 + 2), ps.get(i1 * 6 + 3));
                    }
                    break;
                }
                case 's': {
                    ArrayList<Float> ps = findNum(i);
                    Log.e(TAG, "parserSvgPath: findPoints cubPoint:" + ps + "---" + cubPoint);
                    for (int i1 = 0; i1 < ps.size() / 4; i1++) {
                        float x1 = 2 * lastPoint.x - cubPoint.x;
                        float y1 = 2 * lastPoint.y - cubPoint.y;
                        lPath.cubicTo(x1, y1, lastPoint.x + ps.get(i1 * 4), lastPoint.y + ps.get(i1 * 4 + 1), lastPoint.x + ps.get(i1 * 4 + 2), lastPoint.y + ps.get(i1 * 4 + 3));
                        Log.e(TAG, "parser: lastPoint s:" + lastPoint);
                        cubPoint.set(lastPoint.x + ps.get(i1 * 4), lastPoint.y + ps.get(i1 * 4 + 1));
                        lastPoint.offset(ps.get(i1 * 4 + 2), ps.get(i1 * 4 + 3));
                    }
                    break;
                }
                case 'S': {//一般S会跟在C或是S命令后面使用，用前一个点做起始控制点
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size() / 4; i1++) {
                        float x1 = 2 * lastPoint.x - cubPoint.x;
                        float y1 = 2 * lastPoint.y - cubPoint.y;
                        lPath.cubicTo(x1, y1, ps.get(i1 * 4), ps.get(i1 * 4 + 1), ps.get(i1 * 4 + 2), ps.get(i1 * 4 + 3));
                        cubPoint.set(ps.get(i1 * 4), ps.get(i1 * 4 + 1));
                        lastPoint.set(ps.get(i1 * 4 + 2), ps.get(i1 * 4 + 3));
                        Log.e(TAG, "parser: lastPoint S:" + lastPoint);
                    }
                    break;
                }
                case 'q': {
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size() / 4; i1++) {
                        lPath.quadTo(lastPoint.x + ps.get(i1 * 4), lastPoint.y + ps.get(i1 * 4 + 1), lastPoint.x + ps.get(i1 * 4 + 2), lastPoint.y + ps.get(i1 * 4 + 3));
                        lastPoint.offset(ps.get(i1 * 4 + 2), ps.get(i1 * 4 + 3));
                        Log.e(TAG, "parser: lastPoint q:" + lastPoint);
                    }
                    break;
                }
                case 'Q': {//二次贝塞尔曲线
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size() / 4; i1++) {
                        lPath.quadTo(ps.get(i1 * 4), ps.get(i1 * 4 + 1), ps.get(i1 * 4 + 2), ps.get(i1 * 4 + 3));
                        lastPoint.set(ps.get(i1 * 4 + 2), ps.get(i1 * 4 + 3));
                        Log.e(TAG, "parser: lastPoint Q:" + lastPoint);
                    }
                    break;
                }
                case 't': {
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size() / 2; i1++) {
                        lPath.quadTo(lastPoint.x, lastPoint.y, lastPoint.x + ps.get(i1 * 2), lastPoint.y + ps.get(i1 * 2 + 1));
                        lastPoint.offset(ps.get(i1 * 2), ps.get(i1 * 2 + 1));
                        Log.e(TAG, "parser: lastPoint t:" + lastPoint);
                    }
                    break;
                }
                case 'T': {//T命令会跟在Q后面使用，用Q的结束点做起始点
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size() / 2; i1++) {
                        lPath.quadTo(lastPoint.x, lastPoint.y, lastPoint.x + ps.get(i1 * 2), lastPoint.y + ps.get(i1 * 2 + 1));
                        lastPoint.set(ps.get(i1 * 2), ps.get(i1 * 2 + 1));
                        Log.e(TAG, "parser: lastPoint T:" + lastPoint);
                    }
                    break;
                }
                case 'a': {
                    ArrayList<Float> ps = findNum(i);
                    Log.e(TAG, "parser: lastPoint :" + ps);
                    for (int i1 = 0; i1 < ps.size() / 7; i1++) {
                        drawArc(lPath, lastPoint.x, lastPoint.y, lastPoint.x + ps.get(i1 * 7 + 5), lastPoint.y + ps.get(i1 * 7 + 6), ps.get(i1 * 7), ps.get(i1 * 7 + 1), ps.get(i1 * 7 + 2), lastPoint.y + ps.get(i1 * 7 + 3), lastPoint.x + ps.get(i1 * 7 + 4));
                        lastPoint.offset(ps.get(i1 * 7 + 5), ps.get(i1 * 7 + 6));
                        Log.e(TAG, "parser: lastPoint a:" + lastPoint);
                    }
                    break;
                }
                case 'A': {//画弧
                    ArrayList<Float> ps = findNum(i);
                    for (int i1 = 0; i1 < ps.size() / 7; i1++) {
                        drawArc(lPath, lastPoint.x, lastPoint.y, ps.get(i1 * 7 + 5), ps.get(i1 * 7 + 6), ps.get(i1 * 7), ps.get(i1 * 7 + 1), ps.get(i1 * 7 + 2), lastPoint.y + ps.get(i1 * 7 + 3), lastPoint.x + ps.get(i1 * 7 + 4));
                        lastPoint.set(ps.get(i1 * 7 + 5), ps.get(i1 * 7 + 6));
                        Log.e(TAG, "parser: lastPoint A:" + lastPoint);
                    }
                    break;
                }
                case 'z':
                case 'Z': {//结束
                    Log.e(TAG, "parser: lPath.close()");
                    lPath.close();
                    if (!lPath.isEmpty()) {
                        path.addPath(lPath);
                    }
                    lPath = new Path();
                    lPath.moveTo(startPoint.x, startPoint.y);
                }
                break;
            }
        }
        if (!lPath.isEmpty()) {
            lPath.close();
            path.addPath(lPath);

        }
        return path;
    }


    private ArrayList<Float> findNum(int cmdIndexInPosition) {
        int cmdIndex = cmdPositions.get(cmdIndexInPosition);
        String pointString = svgPath.substring(cmdIndex + 1, cmdPositions.get(cmdIndexInPosition + 1));
        Log.e(TAG, "findPoints--------------: " + pointString);
        ArrayList<Float> list = new ArrayList<>();
        StringBuilder builder = new StringBuilder();
        for (char c : pointString.toCharArray()) {
            if (Character.isSpaceChar(c) || c == ',') {
                String trim = builder.toString().trim();
                if (!TextUtils.isEmpty(trim)) {
                    list.add(Float.valueOf(builder.toString()));
                }
                builder.setLength(0);
                continue;
            }
            if (c == '-') {
                String trim = builder.toString().trim();
                if (!TextUtils.isEmpty(trim)) {
                    list.add(Float.valueOf(builder.toString()));
                }
                builder.setLength(0);
                builder.append(c);
                continue;
            }
            builder.append(c);

        }
        if (!TextUtils.isEmpty(builder.toString().trim())) {
            list.add(Float.valueOf(builder.toString()));
        }
        Log.e(TAG, "findPoints: " + list);
        return list;
    }

    private static float angle(float x1, float y1, float x2, float y2) {

        return (float) Math.toDegrees(Math.atan2(x1, y1) - Math.atan2(x2, y2)) % 360;
    }

    private static final RectF arcRectF = new RectF();
    private static final Matrix arcMatrix = new Matrix();
    private static final Matrix arcMatrix2 = new Matrix();

    private static void drawArc(Path p, float lastX, float lastY, float x, float y, float rx, float ry, float theta, float largeArc, float sweepArc) {
        Log.d("drawArc", "from (" + lastX + "," + lastY + ") to (" + x + "," +
                y + ") r=(" + rx + "," + ry +
                ") theta=" + theta + " flags=" + largeArc + "," + sweepArc);

        Log.e(TAG, "parser: lastPoint drawArc:" + rx + "==" + ry);

        if (rx == 0 || ry == 0) {
            p.lineTo(x, y);
            return;
        }

        if (x == lastX && y == lastY) {
            return; // nothing to draw
        }

        rx = Math.abs(rx);
        ry = Math.abs(ry);

        final float thetaAngle = theta * (float) Math.PI / 180;
        final float st = (float) Math.sin(thetaAngle);
        final float ct = (float) Math.cos(thetaAngle);

        final float xc = (lastX - x) / 2;
        final float yc = (lastY - y) / 2;
        final float x1t = ct * xc + st * yc;
        final float y1t = -st * xc + ct * yc;

        final float x1ts = x1t * x1t;
        final float y1ts = y1t * y1t;
        float rxs = rx * rx;
        float rys = ry * ry;

        float lambda = (x1ts / rxs + y1ts / rys) * 1.001f; // add 0.1% to be
        // sure that no out
        // of range occurs
        // due to
        // limited precision
        if (lambda > 1) {
            float lambdaSr = (float) Math.sqrt(lambda);
            rx *= lambdaSr;
            ry *= lambdaSr;
            rxs = rx * rx;
            rys = ry * ry;
        }

        final float R = (float) (Math.sqrt((rxs * rys - rxs * y1ts - rys * x1ts) / (rxs * y1ts + rys * x1ts)) * ((largeArc == sweepArc) ? -1 : 1));
        final float cxt = R * rx * y1t / ry;
        final float cyt = -R * ry * x1t / rx;
        final float cx = ct * cxt - st * cyt + (lastX + x) / 2;
        final float cy = st * cxt + ct * cyt + (lastY + y) / 2;

        final float th1 = angle(1, 0, (x1t - cxt) / rx, (y1t - cyt) / ry);
        float dth = angle((x1t - cxt) / rx, (y1t - cyt) / ry, (-x1t - cxt) / rx, (-y1t - cyt) / ry);
        Log.e(TAG, "drawArc:th1:" + th1 + ",dth:" + dth);
        if (sweepArc == 0 && dth > 0) {
            dth -= 360;
        } else if (sweepArc != 0 && dth < 0) {
            dth += 360;
        }

        // draw
        if ((theta % 360) == 0) {
            // no rotate and translate need
            arcRectF.set(cx - rx, cy - ry, cx + rx, cy + ry);
            p.arcTo(arcRectF, th1, dth);
        } else {
            arcRectF.set(-rx, -ry, rx, ry);

            arcMatrix.reset();
            arcMatrix.postRotate(theta);
            arcMatrix.postTranslate(cx, cy);
            arcMatrix.invert(arcMatrix2);
            p.transform(arcMatrix2);
            p.arcTo(arcRectF, th1, dth);
            p.transform(arcMatrix);
        }
    }
}
